package org.jsequences;

import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.Joiner;
import com.google.common.base.Joiner.MapJoiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;

/**
 * A Variant of a {@link FluentIterable} for "Pair"-Datastructures in the form of
 * Iterable[Entry[K,V]]. This is the default structure of java-maps, therefore this iterable is best
 * suited for initializing mutable and immutable maps.
 * <p>
 * Note: Even though this class is very similar to FluentIterable, it does not inherit from it. The
 * reason behind this is that not all Functions of the FluentIterable are useful for pairs - or
 * become unwieldy to use, like transform(Function[Entry[K,V]]).
 * <p>
 * Therefor, this class is independent, even though many of its internals are actually implemented
 * by using FluentIterable under the hood.
 * 
 * @param <K> the entry key
 * @param <V> the entry value
 * @author Eric Giese
 */
public abstract class FluentEntryIterable<K, V> implements Iterable<Entry<K, V>> {

    // To prevent external subclassing
    private FluentEntryIterable() {}

    /**
     * Returns a fluent collection that wraps {@code collection}, or {@code collection} itself if it
     * is already a {@code FluentIterable}.
     */
    public static <K, V> FluentEntryIterable<K, V> from(Iterable<? extends Entry<K, V>> entries) {
        Iterable<Entry<K, V>> iterable = castAsEntryIterable(entries);
        if (entries instanceof FluentEntryIterable) {
            // Casts is safe, since FluentEntryIterable is covariant
            @SuppressWarnings("unchecked")
            FluentEntryIterable<K, V> fluent = (FluentEntryIterable<K, V>) iterable;
            return fluent;
        }
        return new FromIterable<K, V>(iterable);
    }

    /**
     * Returns a fluent collection that wraps {@code map}.
     * This method uses more specialized implementations than the more generic
     * from(Iterable, X).
     */
    public static <K, V> FluentEntryIterable<K, V> from(Map<K, V> map) {
        return new FromMap<K, V>(map);
    }

    /**
     * Returns the iterable on which all core functions are applied.
     * Usually, this will be a Collection like the EntrySet of a Map.
     */
    abstract Iterable<Entry<K, V>> delegate();

    @Override
    public final Iterator<Entry<K, V>> iterator() {
        return delegate().iterator();
    }

    /**
     * Returns a string representation of this fluent iterable in the default format of Maps,
     * {@code k1=v1, k2=v2, ..., en]}.
     */
    @Override
    public final String toString() {
        StringBuilder sb = new StringBuilder().append('{');
        STANDARD_JOINER.appendTo(sb, delegate());
        return sb.append('}').toString();
    }

    /**
     * Determines whether this fluent iterable is empty.
     */
    public final boolean isEmpty() {
        return !iterator().hasNext();
    }

    /**
     * Returns the number of entries.
     * 
     * @see Iterables#size()
     */
    public final int size() {
        return fluent().size();
    }

    /**
     * Returns a view of this fluent iterable that skips its first {@code numberToSkip} elements. If
     * this fluent iterable contains fewer than {@code numberToSkip} elements,
     * the returned fluent iterable skips all of its elements.
     * 
     * @see FluentIterable#skip(int)
     */
    public final FluentEntryIterable<K, V> skip(int numberToSkip) {
        return from(Iterables.skip(delegate(), numberToSkip));
    }

    /**
     * Creates a fluent iterable with the first {@code size} elements of this
     * fluent iterable. If this fluent iterable does not contain that many elements,
     * the returned fluent iterable will have the same behavior as this fluent iterable.
     * The returned fluent iterable's iterator supports {@code remove()} if this
     * fluent iterable's iterator does.
     * 
     * @see FluentIterable#limit(int)
     */
    public final FluentEntryIterable<K, V> limit(int size) {
        return from(Iterables.limit(delegate(), size));
    }

    /**
     * Concatenates this FluentEntryIterable with another Iterable of Entries, like Sequences.concat
     * does for "normal" Iterables. This function can be used to concat multiple collections of
     * entries
     * into one large iterable, but beware that the concat-method does NOT create an union:
     * <p>
     * Multiple keys/values or entries will be retained in the concatenated result in will cause the
     * building of immutable maps to fail.
     * 
     * @param iterable the other iterable to append to the end of the iteration order of this one.
     * @return a fluentiterable over both entries
     */
    public final FluentEntryIterable<K, V> concat(
            Iterable<? extends Entry<? extends K, ? extends V>> iterable) {
        return from(Iterables.concat(delegate(), castAsEntryIterable(iterable)));
    }

    /**
     * Returns an {@code ImmutableList} containing all of the elements from this fluent iterable in
     * proper sequence.
     */
    public final ImmutableList<Entry<K, V>> toList() {
        return fluent().toList();
    }

    /**
     * Returns an {@code ImmutableSet} containing all of the elements from this fluent iterable with
     * duplicates removed.
     */
    public final ImmutableSet<Entry<K, V>> toSet() {
        return fluent().toSet();
    }

    /**
     * Returns an immutable map containing the given entries, in order.
     * 
     * @throws RuntimeException
     *             if duplicate keys are provided
     * @see ImmutableMap#of(Object, Object, Object, Object)
     */
    public ImmutableMap<K, V> toMap() {
        return copyIntoBuilder(new ImmutableMap.Builder<K, V>()).build();
    }

    /**
     * Returns an immutable BiMap containing the given entries, in order.
     * 
     * @throws RuntimeException
     *             if duplicate keys are provided
     * @see ImmutableBiMap#of(Object, Object, Object, Object)
     */
    public ImmutableBiMap<K, V> toBiMap() {
        return copyIntoBuilder(new ImmutableBiMap.Builder<K, V>()).build();
    }

    /**
     * Returns an immutable map containing the entries, with keys sorted
     * by the provided comparator.
     * 
     * @param comparator
     *            the sort order. If you want elements to be sorted by
     *            {@link Comparable#compareTo(Object)}, then use
     *            Ordering.natural().
     * @throws RuntimeException
     *             if duplicate keys are provided
     * @see ImmutableSortedMap#copyOf(Map, Comparator)
     */
    public ImmutableSortedMap<K, V> toSortedMap(Comparator<? super K> comparator) {
        // The Comparator can be used invariant in here
        @SuppressWarnings("unchecked")
        Comparator<K> order = (Comparator<K>) comparator;
        return copyIntoBuilder(new ImmutableSortedMap.Builder<K, V>(order)).build();
    }

    /**
     * Copies all the elements from this fluent iterable to to a {@code collection}.
     * 
     * @param collection the collection to copy elements to
     * @return {@code collection}, for convenience
     */
    public <C extends Collection<Entry<? super K, ? super V>>> C copyInto(C collection) {
        return fluent().copyInto(collection);
    }

    /**
     * Copies all the elements from this fluent iterable to a {@code map}.
     * 
     * @param map the map to copy elements to
     * @return {@code map}, for convenience
     */
    public <M extends Map<? super K, ? super V>> M copyInto(M map) {
        for (Entry<K, V> entry : delegate()) {
            map.put(entry.getKey(), entry.getValue());
        }
        return map;
    }

    /**
     * Casts a covariant iterable to the needed fixed types.
     * This cast is safe because of the covariance of iterables:
     * There are no dangerous operations involved. Well... at least not as long as the entries are
     * immutable, a condition I just expect to be met without further ado.
     * 
     * @param iterable the iterable to cast
     * @return the iterable, casted to be invariant
     */
    @SuppressWarnings("unchecked")
    private static <K, V> Iterable<Entry<K, V>> castAsEntryIterable(
            Iterable<? extends Entry<? extends K, ? extends V>> iterable) {
        return (Iterable<Entry<K, V>>) (Iterable<?>) iterable;
    }

    /** Returns a FluentIterable to delegate to its implementations. */
    private FluentIterable<Entry<K, V>> fluent() {
        return FluentIterable.from(delegate());
    }

    /** Copies all the elements from the delegate into an ImmutableMap.Builder. */
    private <M extends ImmutableMap.Builder<K, V>> M copyIntoBuilder(M builder) {
        for (Entry<K, V> entry : delegate()) {
            builder.put(entry.getKey(), entry.getValue());
        }
        return builder;
    }

    /** The Joiner is used by the toString()-Implementation. */
    private static final MapJoiner STANDARD_JOINER = Joiner.on(", ").useForNull("null").
            withKeyValueSeparator("=");

    /**
     * A generic FluentEntryIterable built on top of any kind of iterable.
     * Since the contract of map (uniqueness of elements) is not known to be fullfilled,
     * this methods has to build the maps with multiple invocations of the
     * Immutable builders.
     */
    private static final class FromIterable<K, V> extends FluentEntryIterable<K, V> {

        private final Iterable<Entry<K, V>> iterable;

        FromIterable(Iterable<Entry<K, V>> iterable) {
            this.iterable = iterable;
        }

        @Override
        Iterable<Entry<K, V>> delegate() {
            return iterable;
        }
    }

    /**
     * FluentEntryIterable implementation which builds directly on top of a Map.
     * The toMap-Implementations of this class are much more efficient since they
     * already know that the contract of Map[K,V] is fullfilled.
     */
    private static final class FromMap<K, V> extends FluentEntryIterable<K, V> {

        private final Map<K, V> map;

        FromMap(Map<K, V> map) {
            this.map = checkNotNull(map);
        }

        @Override
        Iterable<Entry<K, V>> delegate() {
            return map.entrySet();
        }

        @Override
        public ImmutableMap<K, V> toMap() {
            return ImmutableMap.copyOf(map);
        }

        @Override
        public ImmutableBiMap<K, V> toBiMap() {
            return ImmutableBiMap.copyOf(map);
        }

        @Override
        public ImmutableSortedMap<K, V> toSortedMap(Comparator<? super K> comparator) {
            return ImmutableSortedMap.copyOf(map, comparator);
        }

        @Override
        public <M extends Map<? super K, ? super V>> M copyInto(M map) {
            map.putAll(this.map);
            return map;
        }
    }
}
